/**
 * 20220114:升级为同时支持普通模式和XR模式的选择器
 * 20220304:[require] to [import]...jordia
 * 20220627:Raycast.eventSourceType to Raycast.eventSourceTypes
 * 20220706:change [unSelect] to [unselect]
 * 20220716:change the position of [unselectCallback],to get correct [selected]
 * 20220829:1.add property [enableEvents] and [enableCallback],remove parameter[fireEvent] of [select],[unselect]...
 *          2.remove [.clearSelect] from [enabled]
 */
import Raycast from "../general/raycast";
import {EventDispatcher,Vector3,Vector2,Object3D} from 'three';
import Utils from '../general/utils';

const SELECT_MODE={
    single: 'single',
    multi: 'multi'
};
const SELECT_STYLE={
    both: 'both',
    box: 'box',
    pick: 'pick'
};

/**
 * 选择器
 @param camera {Camera} 摄像机
 @param rect {object} rect对象{left,top,width,height}
 @param eventSourceType {string} 事件源类型（'crossEvent','xrEvent'）
 @param startEventSource {CrossEvent|XREvent} 起始事件源
 @param startEvents {[string]} 起始事件
 @param moveEventSource {CrossEvent|XREvent} 移动事件源
 @param moveEvents {[string]} 移动事件
 @param endEventSource {CrossEvent|XREvent} 结束事件源
 @param endEvents {[string]} 结束事件
 @param values {object} 可选参数对象：
            {
                style:Selector.styles.both, {string} 选择样式 pick:点选,box:框选,both:混合
                mode:Selector.modes.multi, {string} 选择模式  single:单选,multi:多选
                enabled:true, {boolean} 是否启用
                autoClear:true, {boolean} 自动清除（框选时影响）
                alwaysClosest:true, {boolean} 总是选择最近（点选时影响）
                pointThreshold:1e-4, {number} 拾取点阈值
                screenToWorldDepth:50, {number} NDC空间到world空间的投射深度
            }

 示例：
 let wEvent=DOMCrossPlatformEvent.createEvent(window);
 wEvent.start();
 let selector=new Selector(camera,rect,Raycast.eventSourceTypes.crossEvent,
     crossEvent,['pointDown'],
     wEvent,['pointMove'],
     wEvent,['pointUp'],
     {});

 let camera=renderer.xr.getCamera();
 let selector=new Selector(camera,null,Raycast.eventSourceTypes.xrEvent,
        xrEvent,['pointDown'],
        xrEvent,['pointMove'],
        xrEvent,['pointUp'],
        {});

 let config=new SelectMeshConfig({});
 selector.addConfig(config);
 selector.mode=Selector.modes.multi;
 selector.style=Selector.styles.both;
 selector.add(...meshes);

 selector.selectCallback=(obj)=>{
        obj.material.emissive=new Color('#ff00ec');
    };
 selector.unselectCallback=(obj)=>{
        obj.material.emissive=new Color('#000000');
    };
 selector.onSelectBoxChange=(camera,state,ps,pe)=>{
        switch (state) {
            case 'start':
                break;
            case 'progress':
                selectLine.visible=true;
                break;
            case 'end':
                selectLine.visible=false;
                break;
        }
        if(!ps)return;
        let pjs=new Vector3().copy(ps).project(camera);
        let pje=new Vector3().copy(pe).project(camera);
        let pjs1=new Vector3(pje.x,pjs.y,pjs.z);
        let pje1=new Vector3(pjs.x,pje.y,pje.z);
        pjs1.unproject(camera);
        pje1.unproject(camera);
        selectLine.geometry.getAttribute('position').copyArray([...]);
        selectLine.geometry.getAttribute('position').needsUpdate=true;
    };

 */
export default class Selector{
    constructor(camera,rect,eventSourceType,
                startEventSource,startEvents,
                moveEventSource,moveEvents,
                endEventSource,endEvents,
                values={}){
        this._camera=camera;
        this._rect=rect;
        this._eventSourceType=eventSourceType;
        this._startRaycast=new Raycast(startEventSource,eventSourceType);
        this._startRaycast.events=startEvents;
        this._moveRaycast=new Raycast(moveEventSource,eventSourceType);
        this._moveRaycast.events=moveEvents;
        this._endRaycast=new Raycast(endEventSource,eventSourceType);
        this._endRaycast.events=endEvents;
        if(camera){
            this._startRaycast.camera=camera;
            this._moveRaycast.camera=camera;
            this._endRaycast.camera=camera;
        }
        if(rect){
            this._startRaycast.rect=rect;
            this._moveRaycast.rect=rect;
            this._endRaycast.rect=rect;
        }
        this._init(values);
    }

    //.........................................................public...................................................

    /**
     * 选择模式（单选，多选）
     * @type {{single: string, multi: string}}
     */
    static get modes(){
        return SELECT_MODE;
    }

    /**
     * 选择类型（点选，框选，混合）
     * @type {{pick: string, box: string, both: string}}
     */
    static get styles(){
        return SELECT_STYLE;
    }

    /**
     * 开始射线更新器
     * @returns {Raycast}
     */
    get startRaycast(){
        return this._startRaycast;
    }

    /**
     * 运动射线更新器
     * @returns {Raycast}
     */
    get moveRaycast(){
        return this._moveRaycast;
    }

    /**
     * 结束射线更新器
     * @returns {Raycast}
     */
    get endRaycast(){
        return this._endRaycast;
    }

    /**
     * 设置相机
     * @param camera
     */
    set camera(camera){
        this._camera=camera;
        this._startRaycast.camera=camera;
        this._moveRaycast.camera=camera;
        this._endRaycast.camera=camera;
    }

    /**
     * 设置相应的视口范围
     * @param rect
     */
    set rect(rect){
        this._rect=rect;
        this._startRaycast.rect=rect;
        this._moveRaycast.rect=rect;
        this._endRaycast.rect=rect;
    }

    /**
     * 获取配置列表
     * @returns {[]}
     */
    get config(){
        return this._config;
    }

    /**
     * 获取事件对象
     * @returns {crossEvent}
     */
    get eventCenter(){
        return this._eventCenter;
    }

    /**
     * 设置选择优先顺序
     * @param classes
     */
    set order(classes){
        this._orders=classes;
    }

    /**
     * 获取选择优先顺序
     * @returns {*|[]}
     */
    get order(){
        return this._orders;
    }

    /**
     * 获取已选取的对象
     * @returns {[]}
     */
    get selected(){
        return this._selected;
    }

    /**
     * 选中时的回调函数
     * @param fuc_obj
     */
    set selectCallback(fuc_obj){
        this._selectCallback=fuc_obj;
    }

    /**
     * 取消选择时的回调函数
     * @param fuc_obj
     */
    set unselectCallback(fuc_obj){
        this._unselectCallback=fuc_obj;
    }

    /**
     * 选取发生变化时的回调函数
     * @param func
     */
    set changeCallback(func){
        this._changeCallback=func;
    }

    /**
     * 选择框变化回调函数
     * @param func_camera_state_pointStart_pointEnd
     */
    set onSelectBoxChange(func_camera_state_pointStart_pointEnd){
        this._onSelectBoxChange=func_camera_state_pointStart_pointEnd;
    }

    /**
     * 获取可选对象
     * @returns {[]}
     */
    get targets(){
        return this._objects;
    }

    /**
     * 选择类型
     */
    get style(){
        return this._style;
    }
    set style(style){
        this._style=style;
    }

    /**
     * 选择模式
     */
    get mode(){
        return this._mode;
    }
    set mode(mode){
        this._mode=mode;
    }

    /**
     * 启用/禁用
     */
    get enabled(){
        return this._enabled;
    }
    set enabled(bol){
        if(this._enabled===bol)return;
        this._enabled=bol;
        this._selectStart = false;
        this._isBoxSelect = false;
        this._enabled?this._addEventListener():this._removeEventListener();
    }

    /**
     * 框选自动清除上一次选择结果
     */
    get autoClear(){
        return this._autoClear;
    }
    set autoClear(bol){
        this._autoClear=bol;
    }

    /**
     * 仅选择最近目标
     */
    get alwaysClosest(){
        return this._alwaysClosest;
    }
    set alwaysClosest(bol){
        this._alwaysClosest=bol;
    }

    /**
     * 拖动阈值
     * @returns {*}
     */
    get pointThreshold(){
        return this._pointThreshold;
    }
    set pointThreshold(num){
        this._pointThreshold=num;
    }

    /**
     * 框选起始点和终点的投影深度
     */
    get screenToWorldDepth(){
        return this._screenToWorldDepth;
    }
    set screenToWorldDepth(num){
        this._screenToWorldDepth=Math.max(num,0.02);
    }

    /**
     * 是否启用事件
     */
    set enableEvents(bol){
        this._enableEvents=bol;
    }
    get enableEvents(){
        return this._enableEvents;
    }

    /**
     * 是否启用回调
     */
    set enableCallback(bol){
        this._enableCallback=bol;
    }
    get enableCallback(){
        return this._enableCallback;
    }

    /**
     * 释放未完成的事件
     */
    releaseUnfinishedEvent() {
        this._selectStart = false;
        this._isBoxSelect = false;
    }

    /**
     * 添加配置
     * @param config
     */
    addConfig(...config){
        for(let cof of config) {
            let idx = this._config.findIndex((c) => c.type === cof.type);
            if(idx>-1)continue;
            this._config.push(cof);
        }
    }

    /**
     * 移除配置
     * @param config
     */
    removeConfig(...config){
        for(let cof of config) {
            let idx = this._config.findIndex((c) => c.type === cof.type);
            if(idx<0)continue;
            this._config.splice(idx,1);
        }
    }

    /**
     * 清除配置
     */
    clearConfig(){
        this._config=[];
    }

    /**
     * 添加可选对象
     * @param objects
     */
    add(...objects) {
        for (let object of objects) {
            let config=this._getConfig(object);
            if (!config) {
                console.warn('no config was found to handle:',object);
                continue;
            }    
            let idx = -1;
            for (let i = 0; i < this._objects.length; i++) {
                if (config.compare(this._objects[i], object)) {
                    idx = i;
                    break;
                }
            }
            if (idx === -1) {
                this._objects.push(object);
            } else {
                console.warn('already exist, or no property config to handle this object!',object,this._objects);
            }

        }
    }

    /**
     * 移除可选对象
     * @param objects
     */
    remove(...objects) {
        for (let object of objects) {
            let config = this._getConfig(object);
            if (!config) return;

            let idx = -1;
            for (let i = 0; i < this._objects.length; i++) {
                if (config.compare(this._objects[i], object)) {
                    idx = i;
                    break;
                }
            }
            if (idx !== -1) {
                this._objects.splice(idx, 1);
            }
        }
    }

    /**
     * 清除可选对象
     */
    clear(){
        this._objects=[];
    }

    /**
     * 销毁
     */
    dispose(){
        this._clearSelect();
        this.clear();
        this._drawDash('end');
        for(let c of this._config){
            c.dispose();
        }
        for(let br of this._breaker){
            br.dispose();
        }
    }

    /**
     * 选择对象
     * @param object
     * @param ignoreMode
     */
    select(object,ignoreMode=false){
        let oriSelect=[...this._selected];
        let tempSelectObj =object;
        if (this._mode === Selector.modes.multi||ignoreMode===true) {
            if(!this.ifSelected(tempSelectObj,false)) this._addToSelected(tempSelectObj);
        }
        if (this._mode === Selector.modes.single&&ignoreMode===false) {
            this._clearSelect();
            this._addToSelected(tempSelectObj);
        }
        if(this._enableCallback&&this._selectCallback)this._selectCallback(object);

        this._fireEndEvent(this._selected);
        this._fireChangeEvent(oriSelect);

    }

    /**
     * 取消选择对象
     * @param object
     */
    unselect(object){
        let index = this.ifSelected(object, true);
        if (index === -1) return;
        let oriSelect=[...this._selected];
        this._selected.splice(index,1);
        if(this._enableCallback&&this._unselectCallback)this._unselectCallback(object);
        this._fireEndEvent(this._selected);
        this._fireChangeEvent(oriSelect);
    }

    /**
     * 清除已选择的对象
     */
    clearSelect() {
        const oriSelected=[];
        let oriLength = this._clearSelect(oriSelected);
        if (oriLength !== 0) this._fireClearEvent();
        if (oriLength !== 0) {
            this._fireChangeEvent(oriSelected);
            this._fireEndEvent(this._selected);
        }
    }

    /**
     * 对象是否被选择
     * @param object
     * @param returnIndex
     * @returns {boolean|number}
     */
    ifSelected(object,returnIndex) {
        let idx = -1;
        let config=this._getConfig(object);
        for (let i = 0; i < this._selected.length; i++) {
            if(!config.checkType(this._selected[i]))continue;
            if (config.compare(object, this._selected[i])) {
                idx = i;
                break;
            }
        }
        if (returnIndex) return idx;
        return !(idx<0);
    }

    /**
     * 添加中断对象
     * @param breakers
     */
    addBreaker(...breakers){
        for(let br of breakers){
            let has=false;
            for(let b of this._breaker){
                if(b.uuid===br.uuid){
                    has=true;
                    break;
                }
            }
            if(!has)this._breaker.push(br);
        }
    }

    /**
     * 移除中断对象
     * @param breakers
     */
    removeBreaker(...breakers){
        for(let br of breakers){
            for(let i=0;i<this._breaker.length;i++){
                if(this._breaker[i].uuid===br.uuid){
                    this._breaker.splice(i,1);
                    break;
                }
            }
        }
    }

    /**
     * 清除中断对象
     */
    clearBreaker(){
        this._breaker=[];
    }

    //.........................................................private..................................................

    _init(values){
        this._initVariable(values);
        this._initEventBind();
        this._addEventListener();
    }
    _initVariable(values) {
        this._config=[];
        this._breaker=[];
        this._style=values.style||Selector.styles.both;
        this._eventCenter=new EventDispatcher();
        this._mode=values.mode||Selector.modes.multi;
        this._objects = [];
        this._selected = [];
        this._orders = [];
        this._enabled=values.enabled!==undefined?values.enabled:true;
        this._startPointer = new Vector3();
        this._endPointer = new Vector3();
        this._alwaysClosest=values.alwaysClosest!==undefined?values.alwaysClosest:true;
        this._autoClear=values.autoClear!==undefined?values.autoClear:true;
        this._selectStart = false;
        this._selectCount = 0;
        this._selectStartEvent = {type: 'selectStart', paras: {pointer: new Vector2()}};
        this._selectInEvent = {type: 'selectIn', paras: {pointer: new Vector2()}};
        this._selectEndEvent = {type: 'selectEnd', paras: {value: {}, pointer: new Vector2()}};
        this._selectChangeEvent = {type: 'change', paras: {previous:[],current:[]}};
        this._selectClearEvent = {type: 'selectClear'};
        this._pointThreshold=values.pointThreshold||1e-4;
        this._tA0=[];
        this._selectCallback = null;
        this._unselectCallback = null;
        this._changeCallback = null;
        this._tV0=new Vector3();
        this._tV1=new Vector3();
        this._selectBox=null;
        this._isBoxSelect=false;
        this._onSelectBoxChange=null;
        this._screenToWorldDepth=values.screenToWorldDepth||50;
        this._enableEvents=true;
        this._enableCallback=true;
    }
    _getConfig(object){
        for (let cof of this._config) {
            if (cof.checkType(object)) {
                return cof;
            }
        }
        return null;
    }
    _initEventBind(){
        this._onPointerDownBind=(e)=>{
            this._onPointerDown(e);
        };
        this._onPointerMoveBind=(e)=>{
            this._onPointerMove(e);
        };
        this._onPointerUpBind=(e)=>{
            this._onPointerUp(e);
        };
        this._fireStartEvent=(pointer=new Vector2())=>{
            if(!this._enableEvents)return;
            this._selectStartEvent.paras.pointer.copy(pointer);
            this._eventCenter.dispatchEvent(this._selectStartEvent);
        };
        this._fireSelectInEvent=(pointer=new Vector2())=>{
            if(!this._enableEvents)return;
            this._selectInEvent.paras.pointer.copy(pointer);
            this._eventCenter.dispatchEvent(this._selectInEvent);
        };
        this._fireEndEvent=(selected,pointer)=>{
            if(!this._enableEvents)return;
            this._selectEndEvent.paras.value=selected;
            this._selectEndEvent.paras.pointer.copy(pointer?pointer:this._endPointer);
            this._eventCenter.dispatchEvent(this._selectEndEvent);
        };
        this._fireChangeEvent=(oriSelected=[])=>{
            if(!this._enableEvents)return;
            let isChange=this._checkChanged(oriSelected);
            if(!isChange)return;
            this._selectChangeEvent.paras.previous=oriSelected;
            this._selectChangeEvent.paras.current=this._selected;
            if(this._enableCallback&&this._changeCallback)this._changeCallback();
            this._eventCenter.dispatchEvent(this._selectChangeEvent);
        };
        this._fireClearEvent=()=>{
            if(!this._enableEvents)return;
            this._eventCenter.dispatchEvent(this._selectClearEvent);
        };
    }
    _checkChanged(oriSelected){
        if(oriSelected.length!==this._selected.length)return true;
        for(let j=0;j<oriSelected.length;j++) {
            let cofO=this._getConfig(oriSelected[j]);
            let has=false;
            for (let i = 0; i < this._selected.length; i++) {
                if(!cofO.checkType(this._selected[i]))continue;
                if (cofO.compare(oriSelected[j], this._selected[i])){
                    has=true;
                    break;
                }
            }
            if(!has)return true;
        }
        return false;
    }
    _addEventListener(){
        for(let typ of this._startRaycast.events){
            this._startRaycast.eventCenter.addEventListener(typ,this._onPointerDownBind);
        }
        for(let typ of this._moveRaycast.events){
            this._moveRaycast.eventCenter.addEventListener(typ,this._onPointerMoveBind);
        }
        for(let typ of this._endRaycast.events){
            this._endRaycast.eventCenter.addEventListener(typ,this._onPointerUpBind);
        }
    }
    _removeEventListener(){
        for(let typ of this._startRaycast.events){
            this._startRaycast.eventCenter.removeEventListener(typ,this._onPointerDownBind);
        }
        for(let typ of this._moveRaycast.events){
            this._moveRaycast.eventCenter.removeEventListener(typ,this._onPointerMoveBind);
        }
        for(let typ of this._endRaycast.events){
            this._endRaycast.eventCenter.removeEventListener(typ,this._onPointerUpBind);
        }
    }
    _onPointerDown(event){
        if (this._selectStart === true || this._enabled === false)return;
        this._getEventPointer(event,this._startPointer);
        this._fireStartEvent(this._startPointer);
        this._selectStart = true;
    }
    _onPointerMove(event){
        if (this._selectStart === false || this._enabled === false)return;
        for(let b of this._breaker){
            b.interrupt=true;
        }
        this._getEventPointer(event,this._endPointer);
        let bool=this._startPointer.distanceToSquared(this._endPointer)<this._pointThreshold;
        if (this._style === Selector.styles.pick) {
            if (!bool) this._selectStart = false;
            return;
        }
        if ((!this._isBoxSelect)&&(!bool)) {
            this._isBoxSelect=true;
            this._drawDash('start',this._startPointer,this._endPointer);
        } else if(this._isBoxSelect){
            this._drawDash('progress',this._startPointer,this._endPointer);
        }
        this._fireSelectInEvent(this._endPointer);
    }
    _onPointerUp(event){
        if (this._enabled === false || this._selectStart === false)return;
        for(let b of this._breaker){
            b.interrupt=false;
        }
        this._getEventPointer(event,this._endPointer);
        let oriSelect=this._tA0;
        oriSelect.length=0;
        oriSelect.push(...this._selected);
        if (this._isBoxSelect) {
            this._isBoxSelect=false;
            this._boxSelect();
            this._fireEndEvent(this._selected,this._endPointer);
            this._fireChangeEvent(oriSelect);
        } else {
            if(this._startPointer.distanceToSquared(this._endPointer)<this._pointThreshold) {
                this._pickSelect();
                this._fireEndEvent(this._selected, this._endPointer);
                this._fireChangeEvent(oriSelect);
            }
        }
        this._selectStart = false;
    }
    _clearSelect(oriSelected=[]) {
        let oriLength = this._selected.length;
        oriSelected.push(...this._selected);
        this._selected = [];

        oriSelected.forEach((o) => {
            if (this._enableCallback&&this._unselectCallback) this._unselectCallback(o);
        });
        return oriLength;
    }
    _boxSelect(){
        this._drawDash('end');
        let count = 0;
        let objects=this._objects;
        for(let object of objects){
            let config=this._getConfig(object);
            if(!config)continue;
            if(config.intersectRect(object,this._startPointer,this._endPointer,this._camera)) {
                if (this.ifSelected(object, false)) {
                    this.unselect(object, false);
                    count++;
                } else {
                    this.select(object, false);
                    count++;
                    if (this._mode === Selector.modes.single) break;
                }
            }
        }
        if(this._autoClear){
            if(count===0)this._clearSelect();
        }
    }
    _pickSelect(){
        if (this._style !== Selector.styles.box) {
            let intersect = this._intersectObjects(this._endRaycast,this._objects);
            if (intersect) {
                if (this.ifSelected(intersect.object, false)) {
                    this.unselect(intersect.object, false);
                } else {
                    this.select(intersect.object, false);
                }
            } else {
                this._clearSelect();
            }
        }
    }
    _addToSelected(obj){
        this._selected.push(obj);
    }
    _intersectObjects(raycast,objects){
        let intersections=[];
        for(let object of objects){
            let info=[];
            let config=this._getConfig(object);
            if(!config)continue;
            config.intersectPointer(object,raycast.raycaster,info);
            intersections.push(...info);
        }
        if(intersections.length===0)return null;
        if(!this._alwaysClosest) {
            if (this._selectCount > intersections.length - 1) this._selectCount = 0;
            let result = intersections[this._selectCount] ? intersections[this._selectCount] : false;
            this._selectCount++;
            return result;
        }else{
            let indices=[];
            for(let i=0;i<this._orders.length;i++){
                indices.push(null);
            }
            for(let i=0;i<intersections.length;i++){
                let temp=intersections[i].object;
                for(let j=0;j<indices.length;j++){
                    if(indices[j]===null&&(temp instanceof this._orders[j])){
                        indices[j]=i;
                        if(j===0)return intersections[i];
                    }
                }
            }
            for(let i=0;i<indices.length;i++){
                if(indices[i]!==null)return intersections[indices[i]];
            }
            return intersections[0];
        }
    }
    _drawDash(state,startPointer,endPointer){
        if(this._onSelectBoxChange)this._onSelectBoxChange(this._camera,state,startPointer,endPointer);
    }
    _getEventPointer(event,pointerOut){
        switch (this._eventSourceType) {
            case Raycast.eventSourceTypes.crossEvent:
                let pointer = event.data.isTouch ? event.data.changedTouches[0] : event.data;
                this._tV1.set(pointer.clientX,pointer.clientY,0);
                Utils.screenPointToWorld(this._rect,this._camera,
                    this._screenToWorldDepth,this._tV1,true,1,false,pointerOut);
                break;
            case Raycast.eventSourceTypes.xrEvent:
                let data=event.data;
                let obj=data.object;
                if(!(obj instanceof Object3D))return;
                obj.getWorldPosition(pointerOut);
                break;
        }
    }
}

